/*
 * Copyright (c) 2006 KOZUKA Masahiro  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <glib.h>
#include <assert.h>

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "adaptation.h"
#include "distribution.h"
#include "asconf.h"
#include "auth.h"
#include "bundling.h"
#include "pathmanagement.h"
#include "auxiliary.h"


int
asconf_get_address_from_chunk(SCTP_asconf_chunk *chunk, union sockunion *address)
{
    unsigned short length = 0;
    
    event_logii(VVERBOSE, "chunk(%p), address(%p)", chunk, address);

    length = CHUNKP_LENGTH((SCTP_chunk_header *)chunk);

    if (length < FIXED_ASCONF_CHUNK_SIZE) {
	error_logii(ERROR_MAJOR, "chunk too short: %d(needed), %d(actual)",
	    FIXED_AUTH_CHUNK_SIZE, length);
	return -1;
    }

    switch (ntohs(chunk->addressParameterHeader.param_type)) {    
    case VLPARAM_IPV4_ADDRESS:
	if ((length - FIXED_ASCONF_CHUNK_SIZE) < 4) {
	    error_logii(ERROR_MAJOR, "chunk too short: %d(needed), %d(actual)",
		FIXED_AUTH_CHUNK_SIZE + 4, length);
	    return -1;
	}
	memset(address, 0, sizeof(*address));
	address->sa.sa_family = AF_INET;
	memcpy(&address->sin.sin_addr, chunk->variableParams, 4);
	break;
    case VLPARAM_IPV6_ADDRESS:
	if ((length - FIXED_ASCONF_CHUNK_SIZE) < 16) {
	    error_logii(ERROR_MAJOR, "chunk too short: %d(needed), %d(actual)",
		FIXED_AUTH_CHUNK_SIZE + 16, length);
	    return -1;
	}
	memset(address, 0, sizeof(*address));
	address->sa.sa_family = AF_INET6;
	memcpy(&address->sin6.sin6_addr, chunk->variableParams, 16);
	break;
    default:
	error_logi(ERROR_MAJOR, "Unknown Address Parameter Type(%d)",
	    ntohs(chunk->addressParameterHeader.param_type));
	return -1;
    }

    return 0;
}


typedef struct ASCONF_REQUEST_DATA {
    int			type;
    unsigned long	correlation_id;
    union sockunion	address;
    SCTP_asconf_parameter *param;
} asconf_request_data;

typedef struct ASCONF_DATA {
    int			peer_supports_addip;
    unsigned int	association_id;
    unsigned long	serial;    
    unsigned long	peer_serial;    
    unsigned long	initial_serial;    
    unsigned long	initial_peer_serial;    
    unsigned long	correlation_id;

    short		sending_key;
    short		sending_local_key;
    short 		last_sent_key;
    short		last_sent_local_key;
    gboolean		outstanding;
    TimerID		timer;
    GList		*queued_chunks;
    GList		*queued_requests;
    GList		*cached_ack_chunks;

    int			noOfAddingLocalAddresses;
    GArray		*addingLocalAddresses;
    int			noOfDeletingLocalAddresses;
    GArray		*deletingLocalAddresses;
} asconf_data;


static void
asconf_writeLog_send_chunk(asconf_data *, SCTP_asconf_chunk *, const char *, const char *);

int
asconf_init(void)
{
    return 0;
}


void *
asconf_new(unsigned long initial_serial, unsigned long peer_initial_serial)
{
    asconf_data *ptr;

    event_log(VVERBOSE, "asconf_new");
    ptr = malloc(sizeof(*ptr));
    if(!ptr) {
	error_log(ERROR_FATAL, "Malloc failed in asconf_new()");
	return NULL;
    }

    memset(ptr, 0, sizeof(*ptr));
    ptr->peer_supports_addip = 1;
    ptr->association_id = mdi_readAssociationID();
    ptr->serial = initial_serial;
    ptr->initial_serial = initial_serial;
    ptr->peer_serial = peer_initial_serial - 1;
    ptr->initial_peer_serial = peer_initial_serial;
    
    ptr->addingLocalAddresses =
	g_array_new(FALSE, FALSE, sizeof(union sockunion));
    ptr->deletingLocalAddresses =
	g_array_new(FALSE, FALSE, sizeof(union sockunion));
    ptr->outstanding = FALSE;
    
    ptr->sending_key = -1;
    ptr->sending_local_key = -1;

    return ptr;
}


int
asconf_delete(void *ptr)
{
    asconf_data *ascd = (asconf_data *)ptr;
    SCTP_asconf_chunk *chunk;
    asconf_request_data *request;
    SCTP_asconf_ack_chunk *ack_chunk;
    size_t i;

    event_logi(VVERBOSE, "asconf_delete: ptr(%p)", ptr);

    while (g_list_length(ascd->queued_chunks) > 0) {
	chunk = g_list_nth_data(ascd->queued_chunks, 0);
	ascd->queued_chunks =
	    g_list_remove(
		ascd->queued_chunks,
		g_list_nth_data(ascd->queued_chunks, 0));
	free(chunk);
    }
    g_list_free(ascd->queued_chunks);
    ascd->queued_chunks = NULL;

    while (g_list_length(ascd->queued_requests) > 0) {
	request = g_list_nth_data(ascd->queued_requests, 0);
	ascd->queued_requests = g_list_remove(
	    ascd->queued_requests,
	    g_list_nth_data(ascd->queued_requests, 0));
	if (request->param != NULL) {
	    free(request->param);
	}
	free(request);
    }
    g_list_free(ascd->queued_requests);
    ascd->queued_requests = NULL;

    while (g_list_length(ascd->cached_ack_chunks) > 0) {
	ack_chunk = g_list_nth_data(ascd->cached_ack_chunks, 0);
	ascd->cached_ack_chunks =
	    g_list_remove(
		ascd->cached_ack_chunks,
		g_list_nth_data(ascd->cached_ack_chunks, 0));
	free(ack_chunk);
    }
    g_list_free(ascd->cached_ack_chunks);
    ascd->cached_ack_chunks = NULL;

    for (i = 0; i < ascd->noOfAddingLocalAddresses; i++) {
	ascd->addingLocalAddresses = 
	    g_array_remove_index(ascd->addingLocalAddresses, 0);
    }
    g_array_free(ascd->addingLocalAddresses, FALSE);
    ascd->addingLocalAddresses = NULL;

    for (i = 0; i < ascd->noOfDeletingLocalAddresses; i++) {
	ascd->deletingLocalAddresses = 
	    g_array_remove_index(ascd->deletingLocalAddresses, 0);
    }
    g_array_free(ascd->deletingLocalAddresses, FALSE);
    ascd->deletingLocalAddresses = NULL;

    free(ascd);

    return 0;
}


void
asconf_stop_timer(void)
{
    asconf_data *ascd = NULL;

    event_log(VVERBOSE, "asconf_stop_timer");
    ascd = (asconf_data *)mdi_readSCTP_asconf();
    if (ascd == NULL) {
	error_log(ERROR_MAJOR, "ASCONF instance not set in asconf_stop_timer()");
	return;
    }

    if (ascd->timer != 0) {
	sctp_stopTimer(ascd->timer);
    }
    ascd->timer = 0;
}


/* Check local address's state if all ASCONFs on the fly are accepted.
 * return 1 if local address is added
 * return 0 if local address isn't added nor deleted
 * return -1 if local address is deleted
 */
static int
asconf_check_processed_local_address(asconf_data *ascd, union sockunion *address)
{
    guchar addressStr[SCTP_MAX_IP_LEN];
    int i;
    asconf_request_data *request = NULL;
    unsigned long last_type = 0;
    int added_cnt = 0, deleted_cnt = 0;

    for (i = 0; i < g_list_length(ascd->queued_requests); i++) {
	request = g_list_nth_data(ascd->queued_requests, i);

	adl_sockunion2str(&request->address, addressStr, SCTP_MAX_IP_LEN);
	event_logiiii(VVERBOSE, "asconf_check_processed_local_address: i(%d), type(%d), param(%p), address(%s)",
	    i, request->type, request->param, addressStr);
	if (!adl_equal_address(&request->address, address)) {
	    continue;
	}
	if (request->param != NULL) {
	    continue;
	}
	switch (request->type) {
	case VLPARAM_ADDIP:
	    added_cnt++;
	    break;
	case VLPARAM_DELIP:
	    deleted_cnt++;
	    break;
	default:
	    break;
	}
	last_type = request->type;
    }

    adl_sockunion2str(address, addressStr, SCTP_MAX_IP_LEN);
    event_logiiii(VVERBOSE, "asconf_check_processed_local_address: address(%s), added count(%d), deleted count(%d), last request type(%d)",
	addressStr, added_cnt, deleted_cnt, last_type);

    if (added_cnt == 0 && deleted_cnt == 0) {
	/* address isn't added nor deleted */
	return 0; 
    } else if (deleted_cnt == 0 || added_cnt > deleted_cnt ||
	(last_type == VLPARAM_ADDIP && added_cnt == deleted_cnt)) {
	/* address is added */
	return 1;
    } else {
	/* address is deleted */
	return -1;
    }
}


/* Check local address's state if all requests are accepted.
 * return 1 if local address is added
 * return 0 if local address isn't added nor deleted
 * return -1 if local address is deleted
 */
static int
asconf_check_processing_local_address(asconf_data *ascd, union sockunion *address)
{
    guchar addressStr[SCTP_MAX_IP_LEN];
    int i;
    asconf_request_data *request = NULL;
    unsigned long last_type = 0;
    int adding_cnt = 0, deleting_cnt = 0;

    for (i = 0; i < g_list_length(ascd->queued_requests); i++) {
	request = g_list_nth_data(ascd->queued_requests, i);

	adl_sockunion2str(&request->address, addressStr, SCTP_MAX_IP_LEN);
	event_logiiii(VVERBOSE, "asconf_check_processing_local_address: i(%d), type(%d), param(%p), address(%s)",
	    i, request->type, request->param, addressStr);
	if (!adl_equal_address(&request->address, address)) {
	    continue;
	}
	if (request->param == NULL) {
	    continue;
	}
	switch (request->type) {
	case VLPARAM_ADDIP:
	    adding_cnt++;
	    break;
	case VLPARAM_DELIP:
	    deleting_cnt++;
	    break;
	default:
	    break;
	}
	last_type = request->type;
    }

    adl_sockunion2str(address, addressStr, SCTP_MAX_IP_LEN);
    event_logiiii(VVERBOSE, "asconf_check_processing_local_address: address(%s), adding count(%d), deleting count(%d), last request type(%d)",
	addressStr, adding_cnt, deleting_cnt, last_type);

    if (adding_cnt == 0 && deleting_cnt == 0) {
	/* address isn't added nor deleted */
	return 0; 
    } else if (deleting_cnt == 0 || adding_cnt > deleting_cnt ||
	(last_type == VLPARAM_ADDIP && adding_cnt == deleting_cnt)) {
	/* address is added */
	return 1;
    } else {
	/* address is deleted */
	return -1;
    }
}


static SCTP_asconf_chunk *
asconf_build_chunk(asconf_data *ascd)
{
    SCTP_asconf_chunk *chunk = NULL;
    unsigned short chunk_length = 0;
    unsigned short chunk_offset = 0;

    asconf_request_data *request = NULL;
    SCTP_asconf_parameter *param = NULL;
    unsigned short param_length = 0;

    union sockunion *param_address = NULL;
    guchar param_addressStr[SCTP_MAX_IP_LEN];
    void *addr = NULL;
    unsigned short addrtype = 0;
    size_t addrlen = 0;

    int i, count;


    count = 0;
    for (i = 0; i < g_list_length(ascd->queued_requests); i++) {
	request = g_list_nth_data(ascd->queued_requests, i);
	if (request->param != NULL) {
	    count++;
	}
    }
    event_logii(VVERBOSE, "asconf_build_chunk: num of requests(%d), not on the fly(%d)",
	g_list_length(ascd->queued_requests), count);
	
    if (count == 0) {
	event_log(VVERBOSE, "asconf_build_chunk: All requests on the fly");
	return NULL;
    }

    event_log(VVERBOSE, "asconf_build_chunk: do build");
    count = mdi_getNoOfLocalAddresses();
    for (i = 0; i < count; i++) {
	param_address = mdi_getLocalAddressForIndex(i);
	if (param_address == NULL) {
	    continue;
	}

#if 0
	switch (sockunion_family(param_address)) {
	case AF_INET:
	    if (ntohl(sock2ip(address)) == INADDR_LOOPBACK) {
		address = NULL;
	    }
	    break;
	case AF_INET6:
	    if (!adl_filterInetAddress(address, flag_Default|flag_HideLocal)) {
		address = NULL;
	    }
	    break;
	default:
	    address = NULL;
	    break;
	}
#endif

	if (asconf_check_processed_local_address(ascd, param_address) >= 0) {
	    break;
	}
	param_address = NULL;
    }
    if (param_address == NULL) {
	error_log(ERROR_MAJOR, "asconf_build_chunk: No valid local address");
	return NULL;
    }

    adl_sockunion2str(param_address, param_addressStr, SCTP_MAX_IP_LEN);
    event_logi(VVERBOSE, "asconf_build_chunk: parameter address(%s)",
	param_addressStr);

    switch (sockunion_family(param_address)) {
    case AF_INET:
	addr = &param_address->sin.sin_addr;
	addrtype = VLPARAM_IPV4_ADDRESS;
	addrlen = sizeof(param_address->sin.sin_addr);
	break;
    case AF_INET6:
	addr = &param_address->sin6.sin6_addr;
	addrtype = VLPARAM_IPV6_ADDRESS;
	addrlen = sizeof(param_address->sin6.sin6_addr);
	break;
    }

    chunk_length = FIXED_ASCONF_CHUNK_SIZE + addrlen;

    for (i = 0; i < g_list_length(ascd->queued_requests); i++) {
	request = g_list_nth_data(ascd->queued_requests, i);
	param = request->param;
	if (param == NULL) {
	    continue;
	}
	chunk_length += ntohs(param->vlparam_header.param_length);
    }

    /* Build ASCONF chunk */
    chunk = malloc(chunk_length);
    if (chunk == NULL) {
	error_log(ERROR_FATAL, "Malloc failed in asconf_build_chunk()");
    }
    memset(chunk, 0, chunk_length);
    chunk->chunk_header.chunk_id = CHUNK_ASCONF;
    chunk->chunk_header.chunk_length = htons(chunk_length);

    chunk->serial_number = htonl(ascd->serial);
    ascd->serial++;
    
    chunk->addressParameterHeader.param_type = htons(addrtype);
    chunk->addressParameterHeader.param_length =
	htons(sizeof(SCTP_vlparam_header) + addrlen);
    memcpy(chunk->variableParams, addr, addrlen);

    chunk_offset = FIXED_ASCONF_CHUNK_SIZE + addrlen;

    for (i = 0; i < g_list_length(ascd->queued_requests); i++) {
	request = g_list_nth_data(ascd->queued_requests, i);

	param = request->param;
	if (param == NULL) {
	    continue;
	}
	param_length = ntohs(param->vlparam_header.param_length);

	if (chunk_length <= chunk_offset) {
	   break;
	}
	memcpy(((unsigned char *)chunk) + chunk_offset, param, param_length);

	chunk_offset += param_length;
	while ((chunk_offset) % 4) {
	    chunk_offset++;
	}

	free(param);
	request->param = NULL;
    }

    return chunk;
}


static void asconf_send_chunk_timer(TimerID , void *, void *);

static int
asconf_send_chunk_common(asconf_data *ascd)
{
    union sockunion *dest_addr = NULL;
    union sockunion *local_addr = NULL;

    SCTP_asconf_chunk *chunk = NULL;
    SCTP_asconf_chunk *old_chunk = NULL;
    asconf_request_data *request = NULL;
    int count = 0;

    size_t i;
    guchar dest_addrStr[SCTP_MAX_IP_LEN];
    guchar local_addrStr[SCTP_MAX_IP_LEN];

    /* Check if new request exists */
    count = 0;
    for (i = 0; i < g_list_length(ascd->queued_requests); i++) {
	request = g_list_nth_data(ascd->queued_requests, i);
	if (request->param != NULL) {
	    count++;
	}
    }
    if (count == 0 && g_list_length(ascd->queued_chunks) == 0) {
	/* No sending data */
	return SCTP_SUCCESS;
    }

    /* Set destination and source address */
    if (ascd->sending_key < 0) {
	ascd->sending_key = pm_readPrimaryPath();
    }

    if (ascd->sending_key < 0) {
	event_log(VVERBOSE, "asconf_send_chunk_common: No valid destination");
	return -1;
    } else if ((dest_addr = mdi_getAddressForKey(ascd->sending_key)) == NULL) {
	error_logi(ERROR_MAJOR, "asconf_send_chunk_common: Invalid sending_key=%d",
	    ascd->sending_key);
	return -1;
    }
    adl_sockunion2str(dest_addr, dest_addrStr, SCTP_MAX_IP_LEN);

    /* Selection from all local addresses (including non-ACKed) */
    ascd->sending_local_key = pm_getLocalAddressKey(ascd->sending_key, 1);

    if (ascd->sending_local_key < 0) {
	event_logi(VVERBOSE, "asconf_send_chunk_common: No valid source, dest_addr=%s",
	    dest_addrStr);
	return -1;
    } else if ((local_addr = mdi_getLocalAddressForKey(ascd->sending_local_key)) == NULL) {
	error_logi(ERROR_MAJOR, "asconf_send_chunk_common: Invalid src key(%d)",
	    ascd->sending_local_key);
	return -1;
    }
    adl_sockunion2str(local_addr, local_addrStr, SCTP_MAX_IP_LEN);

    event_logii(VVERBOSE, "asconf_send_chunk_common: %s -> %s",
	local_addrStr, dest_addrStr);

    /* Build ASCONF chunk from queued requests */
    chunk = asconf_build_chunk(ascd);

    count = 0;
    /* together with old ASCONFs */
    for (i = 0; i < g_list_length(ascd->queued_chunks); i++) {
	old_chunk = g_list_nth_data(ascd->queued_chunks, i);
	asconf_writeLog_send_chunk(ascd, old_chunk, local_addrStr, dest_addrStr);
	bu_put_Ctrl_Chunk((SCTP_simple_chunk *)old_chunk,
	    (short *)&ascd->sending_key, (short *)&ascd->sending_local_key);
	count++;
    }
    if (chunk != NULL) {
	asconf_writeLog_send_chunk(ascd, chunk, local_addrStr, dest_addrStr);
	bu_put_Ctrl_Chunk((SCTP_simple_chunk *)chunk,
	    &ascd->sending_key, &ascd->sending_local_key);
	count++;
    }
    if (count == 0) {
	return SCTP_SUCCESS;
    }

    bu_sendAllChunks(&ascd->sending_key, &ascd->sending_local_key);
	
    ascd->outstanding = 1;

    if (chunk != NULL) {
	ascd->queued_chunks = g_list_append(ascd->queued_chunks,
	    chunk);
    }

    if (ascd->timer == 0) {
	ascd->timer = adl_startTimer(pm_readRTO(ascd->sending_key),
	    &asconf_send_chunk_timer, TIMER_TYPE_ASCONF,
	    (void *)&ascd->association_id, NULL);
	event_logii(VVERBOSE, "asconf_send_chunk_common: start timer ID(%d), rto(%d)",
	    ascd->timer, pm_readRTO(ascd->sending_key));
    } else {
	adl_restartTimer(ascd->timer, pm_readRTO(ascd->sending_key));
	event_logii(VVERBOSE, "asconf_send_chunk_common: restart timer ID(%d), rto(%d)",
	    ascd->timer, pm_readRTO(ascd->sending_key));
    }

    ascd->last_sent_key = ascd->sending_key;
    ascd->last_sent_local_key = ascd->sending_local_key;
    ascd->sending_key = -1;
    ascd->sending_local_key = -1;


    return SCTP_SUCCESS;
}


int
asconf_send_chunk(void)
{
    asconf_data *ascd = NULL;

    ascd = (asconf_data *)mdi_readSCTP_asconf();
    if (ascd == NULL) {
	error_log(ERROR_MAJOR, "ASCONF instance not set in asconf_send_chunk()");
	return -1;
    }

    event_logi(VVERBOSE, "asconf_send_chunk: outstanding(%d)",
	ascd->outstanding);

#if 0
    if (ascd->outstanding) {
	return -1;
    }
#endif

    asconf_send_chunk_common(ascd);

    return 0;
}


static void
asconf_send_chunk_timer(TimerID timer_id, void *association_id_ptr, void *unused)
{
    unsigned int association_id;
    asconf_data *ascd = NULL;
    int association_failed = 0;

    association_id = *((unsigned int *)association_id_ptr);

    event_logi(VVERBOSE, "asconf_send_chunk_timer: association_id(%d)",
	association_id);

    if (mdi_setAssociationData(association_id)) {
	error_logi(ERROR_MAJOR, "asconf_send_chunk_timer: association(%u) does not exist", association_id);
	return;
    }
    
    ascd = (asconf_data *)mdi_readSCTP_asconf();
    if (ascd == NULL) {
	error_log(ERROR_MAJOR, "ASCONF instance not set in asconf_send_chunk_timer()");
	return;
    }

    ascd->timer = 0;

    association_failed = pm_chunksRetransmitted(ascd->last_sent_key,
	ascd->last_sent_local_key);
    if (association_failed) {
	mdi_deleteCurrentAssociation();
	mdi_communicationLostNotif(SCTP_COMM_LOST_EXCEEDED_RETRANSMISSIONS);

        event_log(INTERNAL_EVENT_0, "asconf_send_chunk_timer: communication lost");
	mdi_clearAssociationData();
	return;
    }

    ascd->sending_key = pm_getAltPath(ascd->last_sent_key, 1);

    asconf_send_chunk_common(ascd);

    mdi_clearAssociationData();
}


static int
asconf_send_ack_chunk(asconf_data *ascd, short dest_key, short local_key)
{
    SCTP_asconf_ack_chunk *ack_chunk;
    int count = 0;
    int i;

    event_logii(VVERBOSE, "asconf_send_ack_chunk: dest_key(%d), local_key(%d)",
	dest_key, local_key);

    for (i = 0; i < g_list_length(ascd->cached_ack_chunks); i++) {
	ack_chunk = g_list_nth_data(ascd->cached_ack_chunks, i);
	bu_put_Ctrl_Chunk((SCTP_simple_chunk *)ack_chunk, &dest_key, &local_key);
	count++;
    }
    if (count > 0) {
	bu_sendAllChunks(&dest_key, &local_key);
    }

    return 0;
}


static void
asconf_clear_cached_ack(asconf_data *ascd, unsigned long serial)
{
    SCTP_asconf_ack_chunk *ack_chunk;
    unsigned long ack_serial = 0;

    event_logi(VVERBOSE, "asconf_clear_cached_ack: serial(%u)", serial);

    while(g_list_length(ascd->cached_ack_chunks) > 0) {
	event_logi(VVERBOSE, "asconf_clear_cached_ack: g_list_length(ascd->cached_ack_chunks)(%d)",
	    g_list_length(ascd->cached_ack_chunks));
	ack_chunk = g_list_nth_data(ascd->cached_ack_chunks, 0);

	ack_serial = ntohl(ack_chunk->serial_number);
	event_logi(VVERBOSE, "asconf_clear_cached_ack: ack_serial(%u)",
	    ack_serial);

	if (before(ack_serial, serial)) {
	    ascd->cached_ack_chunks = g_list_remove(
		ascd->cached_ack_chunks,
		g_list_nth_data(ascd->cached_ack_chunks, 0));
	    free(ack_chunk);
	} else {
	    break;
	}
    }
}


static int 
asconf_get_address_from_parameter(SCTP_ip_address *param, unsigned short length,
    union sockunion *address)
{
    memset(address, 0, sizeof(*address));

    switch (ntohs(param->vlparam_header.param_type)) {
    case VLPARAM_IPV4_ADDRESS:
	if (length < IPV4_ADDRESS_PARAMETER_SIZE) {
	    error_logii(ERROR_MAJOR, "parameter too short: %d(needed), %d(actual)",
		IPV4_ADDRESS_PARAMETER_SIZE, length);
		    
	    return SCTP_BUFFER_TOO_SMALL;
	}
	address->sa.sa_family = AF_INET;
	memcpy(&address->sin.sin_addr, &param->dest_addr, 4);
	break;
    case VLPARAM_IPV6_ADDRESS:
	if (length < IPV6_ADDRESS_PARAMETER_SIZE) {
	    error_logii(ERROR_MAJOR, "parameter too short: %d(needed), %d(actual)",
		IPV6_ADDRESS_PARAMETER_SIZE, length);
		    
	    return SCTP_BUFFER_TOO_SMALL;
	}
	address->sa.sa_family = AF_INET6;
	memcpy(&address->sin6.sin6_addr, &param->dest_addr, 16);
	break;
    default:
	error_logi(ERROR_MAJOR, "Invalid Address Parameter: %d",
	    ntohs(param->vlparam_header.param_type));
	return SCTP_NOT_SUPPORTED;
    }

    return SCTP_SUCCESS;
}


static SCTP_asconf_indication *
asconf_build_success_indication(SCTP_asconf_ack_chunk *chunk,
    unsigned short length, unsigned short *offset, unsigned long correlation_id)
{
    SCTP_asconf_indication *success_indication;

    if (length - *offset < FIXED_ASCONF_INDICATION_SIZE) {
	return NULL;
    }
    success_indication = (SCTP_asconf_indication *)
	(((unsigned char *)chunk) + *offset);

    success_indication->vlparam_header.param_type =
	htons(VLPARAM_SUCCESS_REPORT);
    success_indication->vlparam_header.param_length =
	htons(FIXED_ASCONF_INDICATION_SIZE);
    success_indication->correlation_id = correlation_id;

    *offset += FIXED_ASCONF_INDICATION_SIZE;
    return success_indication;
}


static
SCTP_asconf_indication *
asconf_build_error_indication(SCTP_asconf_ack_chunk *chunk,
    unsigned short length, unsigned short *offset, unsigned long correlation_id,
    unsigned short cause_code, unsigned char *cause, unsigned short cause_length)
{
    SCTP_asconf_indication *error_indication;
    SCTP_error_cause *error_cause;

    if (length - *offset <
	FIXED_ASCONF_INDICATION_SIZE + FIXED_ERROR_CAUSE_SIZE + cause_length) {
	return NULL;
    }
    error_indication = (SCTP_asconf_indication *)
	(((unsigned char *)chunk) + *offset);
    error_cause = (SCTP_error_cause *)error_indication->variable;

    error_indication->vlparam_header.param_type =
	htons(VLPARAM_ERROR_CAUSE_INDICATION);
    error_indication->vlparam_header.param_length =
	htons(FIXED_ASCONF_INDICATION_SIZE + FIXED_ERROR_CAUSE_SIZE + cause_length);
    error_indication->correlation_id = correlation_id;

    error_cause->cause_code = htons(cause_code);
    error_cause->cause_length = htons(FIXED_ERROR_CAUSE_SIZE + cause_length);
    memcpy(error_cause->cause_information, cause, cause_length);

    *offset += FIXED_ASCONF_INDICATION_SIZE + FIXED_ERROR_CAUSE_SIZE + cause_length;
    return error_indication;
}


static
SCTP_asconf_indication *
asconf_process_add_ip_address(SCTP_asconf_parameter *param,
    unsigned short length, SCTP_asconf_ack_chunk *ack_chunk,
    unsigned short ack_length, unsigned short *ack_offset, int error_cnt)
{
    SCTP_ip_address *address_param;
    unsigned short address_length;

    union sockunion address;
    int result = 0;
    SCTP_asconf_indication *indication = NULL;

    if (length < FIXED_ASCONF_PARAMETER_SIZE + sizeof(SCTP_vlparam_header)) {
	return NULL;
    }

    address_param = (SCTP_ip_address *)param->variableParams;
    address_length = ntohs(address_param->vlparam_header.param_length);
    if (length < FIXED_ASCONF_PARAMETER_SIZE + address_length) {
	return NULL;
    }

    result = asconf_get_address_from_parameter(address_param,
	address_length, &address);
    if (result != SCTP_SUCCESS) {
	if (result == SCTP_NOT_SUPPORTED) {
	    indication = asconf_build_error_indication(ack_chunk,
		ack_length, ack_offset, param->correlation_id,
		ECC_UNRESOLVABLE_ADDRESS, (unsigned char *)address_param,
		address_length);

	    return indication;
	}
	return NULL;
    }

    event_logi(VERBOSE, "Received an ASCONF Add IP Address: %d",
	ntohl(param->correlation_id));
    result = mdi_addDestinationAddressToAssoc(&address);

    if (result != SCTP_SUCCESS) {
	indication = asconf_build_error_indication(ack_chunk,
	    ack_length, ack_offset, param->correlation_id,
	    ECC_OP_REFUSED_NO_RESOURCES, (unsigned char *)address_param,
	    address_length);
	    
    } else if (result == SCTP_SUCCESS && error_cnt > 0){
	indication = asconf_build_success_indication(ack_chunk,
	    ack_length, ack_offset, param->correlation_id);
    }

    return indication;
}


static
SCTP_asconf_indication *
asconf_process_delete_ip_address(SCTP_asconf_parameter *param,
    unsigned short length, SCTP_asconf_ack_chunk *ack_chunk,
    unsigned short ack_length, unsigned short *ack_offset, int error_cnt)
{
    SCTP_ip_address *address_param;
    unsigned short address_length;

    union sockunion address;
    int result = 0;
    SCTP_asconf_indication *indication = NULL;

    if (length < FIXED_ASCONF_PARAMETER_SIZE + sizeof(SCTP_vlparam_header)) {
	return NULL;
    }

    address_param = (SCTP_ip_address *)param->variableParams;
    address_length = ntohs(address_param->vlparam_header.param_length);
    if (length < FIXED_ASCONF_PARAMETER_SIZE + address_length) {
	return NULL;
    }

    result = asconf_get_address_from_parameter(address_param,
	address_length, &address);
    if (result != SCTP_SUCCESS) {
	if (result == SCTP_NOT_SUPPORTED) {
	    indication = asconf_build_error_indication(ack_chunk,
		ack_length, ack_offset, param->correlation_id,
		ECC_UNRESOLVABLE_ADDRESS, (unsigned char *)address_param,
		address_length);

	    return indication;
	}
	return NULL;
    }

    event_logi(VERBOSE, "Received an ASCONF Delete IP Address: %d",
	ntohl(param->correlation_id));

    result = mdi_delDestinationAddressFromAssoc(&address);

    if (result != SCTP_SUCCESS) {
	indication = asconf_build_error_indication(ack_chunk,
	    ack_length, ack_offset, param->correlation_id,
	    ECC_DELETE_LAST_IP_FAILED, (unsigned char *)address_param,
	    address_length);
	    
    } else if (result == SCTP_SUCCESS && error_cnt > 0){
	indication = asconf_build_success_indication(ack_chunk,
	    ack_length, ack_offset, param->correlation_id);
    }

    return indication;
}


static
SCTP_asconf_indication *
asconf_process_set_primary_address(SCTP_asconf_parameter *param,
    unsigned short length, SCTP_asconf_ack_chunk *ack_chunk,
    unsigned short ack_length, unsigned short *ack_offset, int error_cnt)
{
    SCTP_ip_address *address_param;
    unsigned short address_length;

    union sockunion address;
    int result = 0;
    SCTP_asconf_indication *indication = NULL;

    if (length < FIXED_ASCONF_PARAMETER_SIZE + sizeof(SCTP_vlparam_header)) {
	return NULL;
    }

    address_param = (SCTP_ip_address *)param->variableParams;
    address_length = ntohs(address_param->vlparam_header.param_length);
    if (length < FIXED_ASCONF_PARAMETER_SIZE + address_length) {
	return NULL;
    }

    result = asconf_get_address_from_parameter(address_param,
	address_length, &address);
    if (result != SCTP_SUCCESS) {
	if (result == SCTP_NOT_SUPPORTED) {
	    indication = asconf_build_error_indication(ack_chunk,
		ack_length, ack_offset, param->correlation_id,
		ECC_UNRESOLVABLE_ADDRESS, (unsigned char *)address_param,
		address_length);

	    return indication;
	}
	return NULL;
    }

    event_logi(VERBOSE, "Received an ASCONF Set Primary IP Address: %d",
	ntohl(param->correlation_id));

    result = mdi_setPrimaryDestinationAddressToAssoc(&address);

    if (result != SCTP_SUCCESS) {
	indication = asconf_build_error_indication(ack_chunk,
	    ack_length, ack_offset, param->correlation_id,
	    ECC_UNRESOLVABLE_ADDRESS, (unsigned char *)address_param,
	    address_length);
	    
    } else if (result == SCTP_SUCCESS && error_cnt > 0){
	indication = asconf_build_success_indication(ack_chunk,
	    ack_length, ack_offset, param->correlation_id);
    }

    return indication;
}


static int
asconf_process_chunk(asconf_data *ascd, SCTP_asconf_chunk *chunk,
    size_t length, unsigned long serial)
{
    size_t offset = 0;
    SCTP_asconf_parameter *param;
    unsigned short param_length = 0;

    SCTP_asconf_ack_chunk *ack_chunk;
    unsigned short ack_chunk_length;
    unsigned short ack_chunk_offset;

    int error_cnt = 0;

    event_logii(VVERBOSE, "asconf_process_chunk: length(%u), serial(%u)",
	length, serial);

    offset += ntohs(chunk->addressParameterHeader.param_length) -
	sizeof(chunk->addressParameterHeader);
    if (length < FIXED_ASCONF_CHUNK_SIZE + offset) {
	error_logii(ERROR_MAJOR, "chunk too short: %d(needed), %d(actual)",
	    FIXED_ASCONF_CHUNK_SIZE + offset, length);
	return SCTP_BUFFER_TOO_SMALL;
    }

    event_logii(VVERBOSE, "asconf_process_chunk: serial(%u), ascd->peer_serial(%u)",
	serial, ascd->peer_serial);

    if (before(serial, ascd->peer_serial + 1)) {
	/* draft-marushin-sctp-asconfext: Section 4.2 M2) */
	return SCTP_SUCCESS;
    } else if (after(serial, ascd->peer_serial + 1)) {
	/* draft-ietf-tsvwg-addip-sctp: Section 4.2 C4) */
	return SCTP_PEER_ERROR;
    }

    ack_chunk = malloc(sizeof(*ack_chunk));
    if (ack_chunk == NULL) {
	error_log(ERROR_FATAL, "Malloc failed in asconf_process_chunk()");
    }
    memset(ack_chunk, 0, sizeof(*ack_chunk));
    ack_chunk_length = sizeof(*ack_chunk);

    ack_chunk->chunk_header.chunk_id = CHUNK_ASCONF_ACK;
    ack_chunk->serial_number = chunk->serial_number;
    ack_chunk_offset = FIXED_ASCONF_ACK_CHUNK_SIZE;

    while (FIXED_ASCONF_CHUNK_SIZE + offset + sizeof(SCTP_vlparam_header) <
	length) {
	param = (SCTP_asconf_parameter *)(chunk->variableParams + offset);
	param_length = ntohs(param->vlparam_header.param_length);
	if (length < FIXED_ASCONF_CHUNK_SIZE + offset + param_length) {
	    error_logii(ERROR_MAJOR, "chunk too short: %d(needed), %d(actual)",
		FIXED_ASCONF_CHUNK_SIZE + offset + param_length, length);
	    return SCTP_BUFFER_TOO_SMALL;
	}

	switch (ntohs(param->vlparam_header.param_type)) {
	case VLPARAM_ADDIP:
	    if (asconf_process_add_ip_address(param, param_length,
		    ack_chunk, ack_chunk_length, &ack_chunk_offset,
		    error_cnt) != NULL) {
		error_cnt++;
	    }
	    break;
	case VLPARAM_DELIP:
	    if (asconf_process_delete_ip_address(param, param_length,
		    ack_chunk, ack_chunk_length, &ack_chunk_offset,
		    error_cnt) != NULL) {
		error_cnt++;
	    }
	    break;
	case VLPARAM_SET_PRIMARY:
	    if (asconf_process_set_primary_address(param, param_length,
		    ack_chunk, ack_chunk_length, &ack_chunk_offset,
		    error_cnt) != NULL) {
		error_cnt++;
	    }
	    break;
	default:
	    if ((ntohs(param->vlparam_header.param_type) & 0x8000) == 0) {
		offset = length - FIXED_ASCONF_CHUNK_SIZE;
	    }
	    error_logi(ERROR_MAJOR, "Invalid ASCONF Parameter(%d)",
		ntohs(param->vlparam_header.param_type));
	    break;
	}
	offset += param_length;

	while ((offset % 4) != 0) {
	    offset++;
	}
    }
    ack_chunk->chunk_header.chunk_length = htons(ack_chunk_offset);

    ascd->peer_serial++;
    ascd->cached_ack_chunks = g_list_append(ascd->cached_ack_chunks, ack_chunk);
	
    return SCTP_SUCCESS;
}


int
asconf_recv_chunk(unsigned char *packet, unsigned short packet_length,
    unsigned short *packet_offset, unsigned short *chunk_length,
    union sockunion *dest_addr, short *dest_keyPtr, short local_key)
{
    asconf_data *ascd = NULL;
    SCTP_asconf_chunk *chunk = NULL;
    unsigned short length = *chunk_length;
    unsigned short offset = 0;

    int count = 0;
    unsigned long serial = 0;
    unsigned long prev_serial = 0;
    unsigned long first_serial = 0;
    int result = 0;
    short dest_key = -1;

    dest_key = *dest_keyPtr;

    event_logiiiii(VVERBOSE, "asconf_recv_chunk: packet(%p), packet_length(%d), *packet_offset(%d), dest_key(%d), local_key(%d)",
	packet, packet_length, *packet_offset, dest_key, local_key);

    ascd = (asconf_data *)mdi_readSCTP_asconf();
    if (!ascd) {
	error_log(ERROR_MAJOR, "ASCONF instance not set in asconf_recv_chunk()");
	return SCTP_MODULE_NOT_FOUND;
    }

    while (packet_length > *packet_offset + offset + sizeof(SCTP_chunk_header)) {
	chunk = (SCTP_asconf_chunk *)(packet + *packet_offset + offset);
	length = CHUNKP_LENGTH((SCTP_chunk_header *)chunk);

	event_logii(VVERBOSE, "asconf_recv_chunk: chunk_id(%d), chunk_length(%d)",
	    chunk->chunk_header.chunk_id, length);

	if (chunk->chunk_header.chunk_id != CHUNK_ASCONF) {
	    break;
	}

	serial = ntohl(chunk->serial_number);

	if (length < FIXED_ASCONF_CHUNK_SIZE) {
	    error_logii(ERROR_MAJOR, "chunk too short: %d(needed), %d(actual)",
		FIXED_ASCONF_CHUNK_SIZE, length);
	} else if (count > 0 && serial != prev_serial + 1) {
	    /* ASCONF chunks must be bundled in order */
	    error_logii(ERROR_MAJOR, "ASCONF chunk not bundled in order: serial(%d), next serial(%d)",
		prev_serial, serial);
	} else {
	    if (count == 0) {
		/* Save first ASCONF chunk's serial. */
		first_serial = serial;
	    }

	    result = asconf_process_chunk(ascd, chunk, length, serial);
	    count++;
	    prev_serial = serial;
	}

	offset += length;
	while (offset % 4)
	    offset++;
	event_logiiii(VVERBOSE, "asconf_recv_chunk: packet(%p), packet_length(%d), *packet_offset(%d), offset(%d)",
	    packet, packet_length, *packet_offset, offset);
    }

    if (dest_key < 0) {
	/* destination address to send ASCONF-ACK should be added. */
	dest_key = mdi_getKeyForAddress(dest_addr);
    }
    if (dest_key < 0) {
	/* respond to primary path */
	dest_key = pm_readPrimaryPath();
    } else {
	*dest_keyPtr = dest_key;
    }

    if (count > 0) {
	asconf_clear_cached_ack(ascd, first_serial);
	asconf_send_ack_chunk(ascd, dest_key, local_key);
    }
    *chunk_length = length;
    *packet_offset += offset;

    return SCTP_SUCCESS;
}


static int
asconf_complete_parameter(asconf_data *ascd, SCTP_asconf_parameter *param,
    SCTP_asconf_indication *indication)
{
    SCTP_ip_address *address_param;
    asconf_request_data *request = NULL;
    union sockunion address;
    int i;

    memset(&address, 0, sizeof(address));

    event_logii(VVERBOSE, "asconf_complete_parameter: param(%p), indication(%p)",
	param, indication);

    for (i = 0; i < g_list_length(ascd->queued_requests); i++) {
	request = g_list_nth_data(ascd->queued_requests, i);
	if (ntohl(param->correlation_id) == request->correlation_id) {
	    ascd->queued_requests =
		g_list_remove(ascd->queued_requests, request);
	    if (request->param != NULL) {
		error_logi(ERROR_MAJOR, "asconf_complete_parameter: request(%p)->param must be NULL!!", request);
		free(request->param);
	    }
	    free(request);
	    break;
	}
    }

    address_param = (SCTP_ip_address *)param->variableParams;
    switch (ntohs(address_param->vlparam_header.param_type)) {
    case VLPARAM_IPV4_ADDRESS:
	address.sa.sa_family = AF_INET;
	memcpy(&address.sin.sin_addr, &address_param->dest_addr, 4);
	break;
    case VLPARAM_IPV6_ADDRESS:
	address.sa.sa_family = AF_INET6;
	memcpy(&address.sin6.sin6_addr, &address_param->dest_addr, 16);
	break;
    default:
	return -1;
    }

    if (indication == NULL ||
	(indication != NULL &&
	ntohs(indication->vlparam_header.param_type) == VLPARAM_SUCCESS_REPORT)) { 
	event_logi(VVERBOSE, "asconf_complete_parameter: ASCONF success for correlation_id(%u)",
	    ntohl(param->correlation_id));

	switch (ntohs(param->vlparam_header.param_type)) {
	case VLPARAM_ADDIP:
	    mdi_addLocalAddressToAssocComplete(&address);
	    break;
	case VLPARAM_DELIP:
	    mdi_delLocalAddressFromAssocComplete(&address);
	    break;
	case VLPARAM_SET_PRIMARY:
	    mdi_setPrimaryLocalAddressToAssocComplete(&address);
	    break;
	default:
	    return -1;
	}
    } else {
	event_logi(VVERBOSE, "asconf_complete_parameter: ASCONF failed for correlation_id(%u)",
	    ntohl(param->correlation_id));

	switch (ntohs(param->vlparam_header.param_type)) {
	case VLPARAM_ADDIP:
	    mdi_addLocalAddressToAssocFail(&address);
	    break;
	case VLPARAM_DELIP:
	    mdi_delLocalAddressFromAssocFail(&address);
	    break;
	case VLPARAM_SET_PRIMARY:
	    break;
	default:
	    return -1;
	}
    }

    return 0;
}


static int
asconf_complete_chunk(asconf_data *ascd, SCTP_asconf_chunk *chunk,
    SCTP_asconf_indication *indication)
{
    unsigned short length = 0;
    size_t offset = 0;
    SCTP_asconf_parameter *param = NULL;
    unsigned short param_length = 0;

    event_logii(VVERBOSE, "asconf_complete_chunk: chunk(%p), indication(%p)",
	chunk, indication);

    length = CHUNKP_LENGTH((SCTP_chunk_header *)chunk);

    offset += ntohs(chunk->addressParameterHeader.param_length) -
	sizeof(chunk->addressParameterHeader);

    while (FIXED_ASCONF_CHUNK_SIZE + offset + sizeof(SCTP_vlparam_header) <
	length) {
	param = (SCTP_asconf_parameter *)(chunk->variableParams + offset);
	param_length = ntohs(param->vlparam_header.param_length);
	if (length < FIXED_ASCONF_CHUNK_SIZE + offset + param_length) {
	    error_logii(ERROR_MAJOR, "chunk too short: %d(needed), %d(actual)",
		FIXED_ASCONF_CHUNK_SIZE + offset + param_length, length);
	    return SCTP_BUFFER_TOO_SMALL;
	}

	if (indication != NULL &&
	    indication->correlation_id == param->correlation_id) {
	    asconf_complete_parameter(ascd, param, indication);
	} else {
	    asconf_complete_parameter(ascd, param, NULL);
	}

	offset += param_length;

	while ((offset % 4) != 0) {
	    offset++;
	}
    }
    return SCTP_SUCCESS;
}


static int
asconf_process_ack_chunk(asconf_data *ascd, SCTP_asconf_ack_chunk *ack_chunk,
    size_t length, unsigned long serial)
{
    SCTP_asconf_chunk *sent_chunk = NULL;
    unsigned long sent_serial = 0;
    size_t offset = 0;

    SCTP_asconf_indication *indication = NULL;
    unsigned short indication_length = 0;

    size_t i;

    event_logii(VVERBOSE, "asconf_process_ack_chunk: length(%d), serial(%d)",
	length, serial);

    /* Find ASCONF for acked serial */
    for (i = 0; i < g_list_length(ascd->queued_chunks); i++) {
	sent_chunk = g_list_nth_data(ascd->queued_chunks, i);
	sent_serial = ntohl(sent_chunk->serial_number);
	if (i == 0 && before(serial, sent_serial)) {
	    /* Already acked */
	    sent_chunk = NULL;
	    break;
	} else if (sent_serial == serial) {
	    break;
	}
	sent_chunk = NULL;
    }
    if (i == 0 && sent_chunk == NULL) {
        event_log(VVERBOSE, "asconf_process_ack_chunk: already acked");
	return SCTP_SUCCESS;
    } else if (sent_chunk == NULL) {
	error_logi(ERROR_MAJOR, "asconf_process_ack_chunk: serial(%d) not found", serial);
	return SCTP_PEER_ERROR;
    }

    mdi_writeLog("%u, ASCONF-OUT\n", serial - (ascd->initial_serial - 1));

    if (length == FIXED_ASCONF_ACK_CHUNK_SIZE) {
	/* Success Indication for all ASCONF's parameters */
	asconf_complete_chunk(ascd, sent_chunk, NULL);
    } else {
	while (FIXED_ASCONF_ACK_CHUNK_SIZE + offset + sizeof(SCTP_vlparam_header) < length) {
	    indication =
		(SCTP_asconf_indication *)(ack_chunk->variableParams + offset);
	    indication_length = ntohs(indication->vlparam_header.param_length);
	    if (length < FIXED_ASCONF_ACK_CHUNK_SIZE + offset + indication_length) {
		error_logii(ERROR_MAJOR, "chunk too short: %d(needed), %d(actual)",
		    FIXED_ASCONF_CHUNK_SIZE + offset + indication_length, length);
		return SCTP_BUFFER_TOO_SMALL;
	    }

	    asconf_complete_chunk(ascd, sent_chunk, indication);

	    offset += indication_length;

	    while ((offset % 4) != 0) {
		offset++;
	    }
	}
    }

    ascd->queued_chunks = g_list_remove(ascd->queued_chunks,
	sent_chunk);
    free(sent_chunk);

    event_logi(VVERBOSE, "asconf_process_ack_chunk: unacked ASCONF num(%d)",
	g_list_length(ascd->queued_chunks));
    if (g_list_length(ascd->queued_chunks) == 0) {
	sctp_stopTimer(ascd->timer);
	ascd->timer = 0;
    }

    return SCTP_SUCCESS;
}


int
asconf_recv_ack_chunk(unsigned char *packet, unsigned short packet_length,
    unsigned short *packet_offset, unsigned short *chunk_length,
    int address_key)
{
    asconf_data *ascd = NULL;
    SCTP_asconf_ack_chunk *chunk = NULL;
    unsigned short length = *chunk_length;
    unsigned short offset = 0;

    int count = 0;
    unsigned long serial = 0;
    unsigned long prev_serial = 0;
    int result = 0;

    event_logiiii(VVERBOSE, "asconf_recv_ack_chunk: packet(%p), packet_length(%d), *packet_offset(%d), address_key(%d)",
	packet, packet_length, *packet_offset, address_key);

    ascd = (asconf_data *)mdi_readSCTP_asconf();
    if (!ascd) {
	error_log(ERROR_MAJOR, "ASCONF instance not set in asconf_recv_ack_chunk()");
	return SCTP_MODULE_NOT_FOUND;
    }

    while (packet_length > *packet_offset + offset + sizeof(SCTP_chunk_header)) {
	chunk = (SCTP_asconf_ack_chunk *)(packet + *packet_offset + offset);
	length = CHUNKP_LENGTH((SCTP_chunk_header *)chunk);

	event_logii(VVERBOSE, "asconf_recv_ack_chunk: chunk_id(%d), chunk_length(%d)",
	    chunk->chunk_header.chunk_id, length);

	if (chunk->chunk_header.chunk_id != CHUNK_ASCONF_ACK) {
	    break;
	}

	if (length < FIXED_ASCONF_ACK_CHUNK_SIZE) {
	    error_logii(ERROR_MAJOR, "chunk too short: %d(needed), %d(actual)",
		FIXED_ASCONF_ACK_CHUNK_SIZE, length);
	} else if (count > 0 &&
	    (serial = ntohl(chunk->serial_number)) != prev_serial + 1) {
	    /* ASCONF-ACK chunks must be bundled in order */
	    error_logii(ERROR_MAJOR, "ASCONF-ACK chunk not bundled in order: serial(%d), next serial(%d)",
		prev_serial, serial);
	} else {
	    serial = ntohl(chunk->serial_number);
	    result = asconf_process_ack_chunk(ascd, chunk, length, serial);
	    count++;
	    prev_serial = serial;
	}

	offset += length;
	while (offset % 4)
	    offset++;
	event_logiiii(VVERBOSE, "asconf_recv_ack_chunk: packet(%p), packet_length(%d), *packet_offset(%d), offset(%d)",
	    packet, packet_length, *packet_offset, offset);
    }
    *chunk_length = length;
    *packet_offset += offset;

    return SCTP_SUCCESS;
}


static SCTP_asconf_parameter *
asconf_build_parameter(asconf_data *ascd, int type, union sockunion *address)
{
    SCTP_asconf_parameter *param = NULL;
    unsigned short length = 0;
    void *addr = NULL;
    unsigned short addrlen = 0;
    unsigned short addrtype = 0;
    SCTP_ip_address *address_param = NULL;
    guchar addressStr[SCTP_MAX_IP_LEN];

    adl_sockunion2str(address, addressStr, SCTP_MAX_IP_LEN);

    event_logii(VVERBOSE, "asconf_build_parameter: type(%d) address(%s)", 
	type, addressStr);
    switch (address->sa.sa_family) {
    case AF_INET:
	length = FIXED_ASCONF_PARAMETER_SIZE + IPV4_ADDRESS_PARAMETER_SIZE;
	addr = &address->sin.sin_addr;
	addrlen = sizeof(address->sin.sin_addr);
	addrtype = VLPARAM_IPV4_ADDRESS;
	break;
    case AF_INET6:
	length = FIXED_ASCONF_PARAMETER_SIZE + IPV6_ADDRESS_PARAMETER_SIZE;
	addr = &address->sin6.sin6_addr;
	addrlen = sizeof(address->sin6.sin6_addr);
	addrtype = VLPARAM_IPV6_ADDRESS;
	break;
    default:
	return NULL;
    }

    param = malloc(length);
    if (param == NULL) {
	error_log(ERROR_FATAL, "Malloc failed in asconf_build_parameter()");
    }
    param->vlparam_header.param_type = htons(type);
    param->vlparam_header.param_length = htons(length);

    param->correlation_id = htonl(ascd->correlation_id);
    ascd->correlation_id++;

    address_param = (SCTP_ip_address *)param->variableParams;
    address_param->vlparam_header.param_type = htons(addrtype);
    address_param->vlparam_header.param_length =
	ntohs(sizeof(SCTP_vlparam_header) + addrlen);
    memcpy(&address_param->dest_addr, addr, addrlen);

    return param;
}


int
asconf_add_ip_address(union sockunion *address)
{
    asconf_data *ascd = NULL;
    asconf_request_data *request = NULL;
    SCTP_asconf_parameter *param = NULL;
    int index;
    guchar addressStr[SCTP_MAX_IP_LEN];

    adl_sockunion2str(address, addressStr, SCTP_MAX_IP_LEN);
    event_logi(VVERBOSE, "asconf_add_ip_address: address(%s)", addressStr);

    ascd = (asconf_data *)mdi_readSCTP_asconf();
    if (!ascd) {
	error_log(ERROR_MAJOR, "ASCONF instance not set in asconf_add_ip_address()");
	return SCTP_MODULE_NOT_FOUND;
    }

    if (ascd->peer_supports_addip == 0) {
	event_log(VVERBOSE, "asconf_add_ip_address: ADDIP disabled");
	return SCTP_NOT_SUPPORTED;
    }

    /* First, check wheter local address exists in the association */
    index = mdi_getLocalIndexForAddress(address, 0);
    if (index >= 0) {
	/* Second, check wheter ASCONFs on the fly will delete the local address */
	if (asconf_check_processed_local_address(ascd, address) >= 0 &&
	    asconf_check_processing_local_address(ascd, address) >= 0) {
	    event_logi(VVERBOSE, "asconf_add_ip_address: %s already added",
		addressStr);
	    return SCTP_WRONG_ADDRESS;
	}
    }
    mdi_addLocalAddressToAssoc(address);

    param = asconf_build_parameter(ascd, VLPARAM_ADDIP, address);
    if (param == NULL) {
	return SCTP_OUT_OF_RESOURCES;
    }

    request = (asconf_request_data *)malloc(sizeof(*request));
    if (request == NULL) {
	free(param);
	return SCTP_OUT_OF_RESOURCES;
    }
    memset(request, 0, sizeof(*request));

    request->type = VLPARAM_ADDIP;
    request->correlation_id = ntohl(param->correlation_id);
    memcpy(&request->address, address, sizeof(*address));
    request->param = param;

    ascd->queued_requests =
	g_list_append(ascd->queued_requests, request);

    return SCTP_SUCCESS;
}


int
asconf_delete_ip_address(union sockunion *address)
{
    asconf_data *ascd = NULL;
    asconf_request_data *request = NULL;
    SCTP_asconf_parameter *param = NULL;
    int index;
    guchar addressStr[SCTP_MAX_IP_LEN];

    adl_sockunion2str(address, addressStr, SCTP_MAX_IP_LEN);

    event_logi(VVERBOSE, "asconf_delete_address: address(%s)", addressStr);
    ascd = (asconf_data *)mdi_readSCTP_asconf();
    if (!ascd) {
	error_log(ERROR_MAJOR, "ASCONF instance not set in asconf_delete_ip_address()");
	return SCTP_MODULE_NOT_FOUND;
    }

    if (ascd->peer_supports_addip == 0) {
	event_log(VVERBOSE, "asconf_delete_ip_address: ADDIP disabled");
	return SCTP_NOT_SUPPORTED;
    }

    /* First, check wheter local address exists in the association */
    index = mdi_getLocalIndexForAddress(address, 0);
    if (index < 0) {
	return SCTP_WRONG_ADDRESS;
    }
    /* Second, check wheter ASCONFs on the fly will delete the local address */
    if (asconf_check_processed_local_address(ascd, address) < 0) {
	if (asconf_check_processing_local_address(ascd, address) <= 0) {
	    event_logi(VVERBOSE, "asconf_delete_ip_address: %s already deleted",
		addressStr);
	    return SCTP_WRONG_ADDRESS;
	}
    } else {
	if (asconf_check_processing_local_address(ascd, address) < 0) {
	    event_logi(VVERBOSE, "asconf_delete_ip_address: %s already deleted",
		addressStr);
	    return SCTP_WRONG_ADDRESS;
	}
    }
    mdi_delLocalAddressFromAssoc(address);

    param = asconf_build_parameter(ascd, VLPARAM_DELIP, address);

    request = (asconf_request_data *)malloc(sizeof(*request));
    if (request == NULL) {
	free(param);
	return SCTP_OUT_OF_RESOURCES;
    }
    memset(request, 0, sizeof(*request));

    request->type = VLPARAM_DELIP;
    request->correlation_id = ntohl(param->correlation_id);
    memcpy(&request->address, address, sizeof(*address));
    request->param = param;

    ascd->queued_requests =
	g_list_append(ascd->queued_requests, request);

    return SCTP_SUCCESS;
}


int
asconf_set_primary_ip_address(union sockunion *address)
{
    asconf_data *ascd = NULL;
    asconf_request_data *request = NULL;
    SCTP_asconf_parameter *param = NULL;
    guchar addressStr[SCTP_MAX_IP_LEN];

    adl_sockunion2str(address, addressStr, SCTP_MAX_IP_LEN);

    event_logi(VVERBOSE, "asconf_set_primary_address: %s", addressStr);
    ascd = (asconf_data *)mdi_readSCTP_asconf();
    if (!ascd) {
	error_log(ERROR_MAJOR, "ASCONF instance not set in asconf_set_primary_address()");
	return SCTP_MODULE_NOT_FOUND;
    }


    if (ascd->peer_supports_addip == 0) {
	event_log(VVERBOSE, "asconf_set_primary_ip_address: ADDIP disabled");
	return SCTP_NOT_SUPPORTED;
    }

    param = asconf_build_parameter(ascd, VLPARAM_SET_PRIMARY, address);

    request = (asconf_request_data *)malloc(sizeof(*request));
    if (request == NULL) {
	free(param);
	return SCTP_OUT_OF_RESOURCES;
    }
    memset(request, 0, sizeof(*request));

    request->type = VLPARAM_SET_PRIMARY;
    request->correlation_id = ntohl(param->correlation_id);
    memcpy(&request->address, address, sizeof(*address));
    request->param = param;

    ascd->queued_requests =
	g_list_append(ascd->queued_requests, request);

    return SCTP_SUCCESS;
}


static void
asconf_writeLog_send_chunk(asconf_data *ascd, SCTP_asconf_chunk *chunk,
    const char *src_addressStr, const char *dest_addressStr)
{
    int result = 0;
    size_t length = 0;
    size_t offset = 0;
    unsigned long serial = 0;
    SCTP_asconf_parameter *param = NULL;
    unsigned short param_length = 0;
    SCTP_ip_address *address_param0 = NULL, *address_param = NULL;
    unsigned short address_length0 = 0, address_length = 0;
    union sockunion address0, address;
    guchar addressStr[SCTP_MAX_IP_LEN], addressStr0[SCTP_MAX_IP_LEN];

    length = CHUNKP_LENGTH((SCTP_chunk_header *)chunk);
    offset += ntohs(chunk->addressParameterHeader.param_length) -
	sizeof(chunk->addressParameterHeader);
    if (length < FIXED_ASCONF_CHUNK_SIZE + offset) {
	error_logii(ERROR_MAJOR, "asconf_writeLog_send_chunk: chunk too short: %d(needed), %d(actual)",
	    FIXED_ASCONF_CHUNK_SIZE + offset, length);
	return;
    }
    serial = ntohl(chunk->serial_number);

    address_param0 = (SCTP_ip_address *)&chunk->addressParameterHeader;
    address_length0 = ntohs(address_param0->vlparam_header.param_length);
    result = asconf_get_address_from_parameter(address_param0,
	address_length0, &address0);
    if (result != SCTP_SUCCESS) {
	return;
    }
    adl_sockunion2str(&address0, addressStr0, SCTP_MAX_IP_LEN);

    while (FIXED_ASCONF_CHUNK_SIZE + offset + sizeof(SCTP_vlparam_header) <
	length) {
	param = (SCTP_asconf_parameter *)(chunk->variableParams + offset);
	param_length = ntohs(param->vlparam_header.param_length);
	if (length < FIXED_ASCONF_CHUNK_SIZE + offset + param_length) {
	    error_logii(ERROR_MAJOR, "asconf_writeLog_send_chunk: chunk too short: %d(needed), %d(actual)",
		FIXED_ASCONF_CHUNK_SIZE + offset + param_length, length);
	    return;
	}

	switch (ntohs(param->vlparam_header.param_type)) {
	case VLPARAM_ADDIP:
	case VLPARAM_DELIP:
	case VLPARAM_SET_PRIMARY:
	    address_param = (SCTP_ip_address *)param->variableParams;
	    address_length = ntohs(address_param->vlparam_header.param_length);
	    if (param_length < FIXED_ASCONF_PARAMETER_SIZE + address_length) {
		return;
	    }

	    result = asconf_get_address_from_parameter(address_param,
		address_length, &address);
	    if (result != SCTP_SUCCESS) {
		return;
	    }
	    adl_sockunion2str(&address, addressStr, SCTP_MAX_IP_LEN);

	    mdi_writeLog("%u, ASCONF, %d, %s, %s\n",
		serial - (ascd->initial_serial - 1),
		ntohs(param->vlparam_header.param_type),
		addressStr, addressStr0);
	    break;
	default:
	    break;
	}

	offset += param_length;

	while ((offset % 4) != 0) {
	    offset++;
	}
    }
}
